package com.deviceteam.kezdet.host; import com.deviceteam.kezdet.Log; import com.deviceteam.kezdet.exception.PluginCreateException; import com.deviceteam.kezdet.exception.PluginLoadException; import com.deviceteam.kezdet.exception.PluginVerifyException; import com.deviceteam.kezdet.interfaces.IPlugin; import com.google.dexmaker.AppDataDirGuesser; import org.whipplugin.data.bundle.JarVerifier; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.*; import java.util.jar.JarFile; public class PluginLoader { private static final String TAG = "KezdetHostLib::PluginLoader"; private X509Certificate _verificationCert; private ClassLoader _parentLoader; private Map<UUID, ClassLoader> _loaderMap = new HashMap<UUID, ClassLoader>(); private Set<File> _cachedContainers = new HashSet<File>(); /** * PluginLoader constructor * * @param parentLoader ClassLoader object to use as a loader context * @param verificationCert public X509 certificate used to verify that the plugin has been correctly signed */ public PluginLoader(ClassLoader parentLoader, X509Certificate verificationCert) { _parentLoader = parentLoader; _verificationCert = verificationCert; Log.verbose( TAG, "constructed." ); } /** * @param containerStream stream encapsulating the JAR file that contains the plugin to load, the stream will be closed by this method * @return UUID as a unique key identifier for the plugin container, pass this to {@link #loadPlugin} * @throws PluginLoadException if the plugin container cannot be loaded * @throws PluginVerifyException if the plugin container verification does not pass (incorrectly signed or cryptographic failure) * @throws PluginCreateException if the plugin ClassLoader could not be created */ public UUID registerContainer(InputStream containerStream) throws PluginLoadException, PluginVerifyException, PluginCreateException { try { File dexCache = new AppDataDirGuesser().guess(); File containerCacheFile = File.createTempFile( "Generated", ".jar", dexCache ); saveToFile( containerStream, containerCacheFile ); _cachedContainers.add( containerCacheFile ); Log.verbose( TAG, "Container: " + containerCacheFile ); JarFile jf = new JarFile( containerCacheFile ); JarVerifier.verify( jf, new X509Certificate[]{_verificationCert} ); // TBD: loaded reflectively to overcome platform issues String dexCachePath = containerCacheFile.getParent(); Log.verbose( TAG, "dexCache: " + dexCachePath); ClassLoader cl = (ClassLoader) Class.forName( "dalvik.system.DexClassLoader" ) .getConstructor( String.class, String.class, String.class, ClassLoader.class ) .newInstance(containerCacheFile.getPath(), dexCachePath, null, _parentLoader ); UUID key = UUID.randomUUID(); _loaderMap.put( key, cl ); return (key); } catch(IOException e) { throw new PluginLoadException( String.format( "Failed to load plugin container" ), e ); } catch(CertificateException e) { throw new PluginVerifyException( String.format( "Certificate issue while verifying plugin container" ), e ); } catch(SecurityException e) { throw new PluginVerifyException( String.format( "Invalid JAR while verifying plugin container" ), e ); } catch(ClassNotFoundException e) { throw new PluginCreateException( String.format( "Cannot find dalvik.system.DexClassLoader!" ), e ); } catch(NoSuchMethodException e) { throw new PluginCreateException( String.format( "Cannot construct ClassLoader for plugin container" ), e ); } catch(IllegalArgumentException e) { throw new PluginCreateException( String.format( "Cannot instantiate ClassLoader in plugin container" ), e ); } catch(IllegalAccessException e) { throw new PluginCreateException( String.format( "Cannot instantiate ClassLoader in plugin container" ), e ); } catch(InvocationTargetException e) { throw new PluginCreateException( String.format( "Cannot instantiate ClassLoader in plugin container" ), e ); } catch(InstantiationException e) { throw new PluginCreateException( String.format( "Cannot instantiate ClassLoader in plugin container" ), e ); } } /** * Loads a plugin from a container * * @param containerId UUID representing the registered plugin container, see: {@link #registerContainer(InputStream)} * @param className name of the class in the JAR file that implements IPlugin * @return an IPlugin instance if the plugin is successfully loaded * @throws PluginLoadException if the plugin cannot be loaded * @throws PluginVerifyException if the plugin verification does not pass (incorrectly signed or cryptographic failure) * @throws PluginCreateException if the plugin class could not be created (incorrect name or other error) */ public IPlugin loadPlugin(UUID containerId, String className) throws PluginCreateException, PluginLoadException { try { ClassLoader cl = _loaderMap.get( containerId ); if (cl == null) { throw new PluginLoadException( String.format( "Invalid plugin container: %s", containerId.toString() ) ); } Class<?> pluginClass = cl.loadClass( className ); IPlugin plugin = (IPlugin) pluginClass.newInstance(); return (plugin); } catch(SecurityException e) { throw new PluginCreateException( String.format( "Cannot instantiate class %s in plugin", className ), e ); } catch(ClassNotFoundException e) { throw new PluginCreateException( String.format( "Cannot find class %s in plugin", className ), e ); } catch(InstantiationException e) { throw new PluginCreateException( String.format( "Cannot instantiate class %s in plugin", className ), e ); } catch(IllegalArgumentException e) { throw new PluginCreateException( String.format( "Cannot instantiate class %s in plugin", className ), e ); } catch(IllegalAccessException e) { throw new PluginCreateException( String.format( "Cannot instantiate class %s in plugin", className ), e ); } } public void dispose() { _loaderMap.clear(); Iterator<File> i = _cachedContainers.iterator(); while (i.hasNext()) { File container = i.next(); if (container.exists()) { container.delete(); } } _cachedContainers.clear(); Log.verbose( TAG, "disposed." ); } private void saveToFile(InputStream inputStream, File destination) throws IOException { FileOutputStream fos = new FileOutputStream( destination ); copyStream( inputStream, fos ); fos.close(); } private void copyStream(InputStream is, OutputStream os) throws IOException { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = is.read( buffer )) != -1) { os.write( buffer, 0, bytesRead ); } } }